/*
 * Peripheral Simulator (PSim)
 *
 * Copyright (c) 2008 Avadh Patel
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include <psim.h>
#include <sysemu.h>
#include <trace_log.h>
#include <stats.h>
//#include "qemu-common.h"
//#include "qemu-timer.h"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <dlfcn.h>
#include <sys/types.h>
#include <dirent.h>
#include <unistd.h>

#include <cpu-all.h>
#include <block.h>

//global variable simulate
//extern int simulate=0;

FILE *sim_fd;

//Max IOPorts should be in sync with vl.c
#define MAX_IOPORTS 65536
#define MAX_DEVICES 128

//#define SIMULATE_CACHE 1

//Mapping of IO addr to device
static SimDevice* sim_dev_io_addr[3][MAX_IOPORTS];
extern IOPortReadFunc* ioport_read_table[3][MAX_IOPORTS];
extern IOPortWriteFunc* ioport_write_table[3][MAX_IOPORTS];

//Mapping of IO mem addr to device
//SimDevice* sim_dev_mem_addr[];

//Stat database
struct stat_sdb_t* sim_sdb;

//Clock offset used for simulator's time
static int64_t sim_cpu_clock_offset=0;
static int64_t sim_cpu_ticks_offset=0;

//Global table of Devices
SimDevice sim_devices[MAX_DEVICES];
int num_devices;

//We keep pointer of bridge and bus so 
//we dont have to search for them...
static int cpu;
static int pci_bridge;
static int pci_bus;
static int cache;
static int isa_bridge;
static int pci_ide;
static int pci_network_card;

//pointer to qemu structures
static qemu_irq* sim_pic;
static PCIDevice* sim_pci_bridge_dev;
static PCIDevice* sim_isa_bridge_dev;
static PCIBus* sim_pci_bus;
static SimDeviceSMMFunc* smm_func;
static SimDeviceMemInitFunc* sim_mmap_init_func;
static int sim_ide_devfn;
static BlockDriverState** sim_hd_table;
static NICInfo* sim_nicinfo;

SimDevice* sim_get_bridge()
{
  return &sim_devices[pci_bridge];
}

SimDevice* sim_get_cpu()
{
  return &sim_devices[cpu];
}

SimDevice* sim_get_cache()
{
  return &sim_devices[cache];
}

//Stats related functions
struct stat_sdb_t* sim_get_stat_db()
{
  return sim_sdb;
}

void sim_clear_stats()
{
  stat_clear_stats(sim_sdb);
}

//Simulation Init
void sim_init()
{
//  sim_devices = NULL;
  num_devices = 0;
  pci_bridge = -1;
  pci_bus = -1;
  cpu = -1;
  cache = -1;
  isa_bridge = -1;
  pci_ide = -1;
  pci_network_card = -1;

  sim_fd = fopen("./sim_log", "w+");
  assert(sim_fd);

  simdebug("Starting Simulation Debug\n");

  //create database
  sim_sdb = stat_new();
  sim_reg_stats(sim_sdb);

  //init the tracer
  trace_init();

  //init message system
  init_messages();

  //Dynamic Library Reading and attaching devices to simulator..
//  char file_name[] = "./so_test.so";
  DIR* libDir = opendir("./libs");
  if(libDir == NULL) {
    fprintf(stderr,"Error opening 'libs' directory\n");
    exit(-1);
  }

  //change current dir
//  chdir("./libs");
  void* test_handler;
  struct dirent* so_file = NULL;
  char file_name[128]; //fix it later
  while((so_file = readdir(libDir)) != NULL) {
    if(strcmp(so_file->d_name, ".") == 0 || 
	strcmp(so_file->d_name, "..") == 0) {
      continue;
    }
    strcpy(file_name,"./libs/\0");
    strcat(file_name,so_file->d_name);
    printf("File name is : %s\n",file_name);
    test_handler = dlopen(file_name, RTLD_NOW);
    if(test_handler == NULL)
    {
      fprintf(stderr,"error opening %s file\n %s",file_name, dlerror());
      exit(-1);
      continue;
    }
    //clear any error
    dlerror();
    void (*dev_init)(void*);
    *(void**)(&dev_init) = dlsym(test_handler, "device_load");
    char* error;
    if((error = dlerror()) != NULL)
    {
      fprintf(stderr,"error reading function\n");
      continue;
    }
    (*dev_init)(test_handler);
  }

  //check if all the necessary modules are loaded
  if(pci_bridge == -1 || pci_ide == -1
      || pci_network_card == -1) {
    fprintf(stderr,"PANIC!! Unable to find necessary modules to boot PC");
    exit(-1);
  }

  
  return;
}

//Dump database to stat file..
void sim_dump_stats(FILE *fd)
{
  fprintf(fd, "\n*****Simulation Stats****\n");
  stat_print_stats(sim_sdb, fd);
  fprintf(fd, "\n");
}

void sim_dump_stats1(const char* filename)
{
  FILE* fd = NULL;
  if(filename == NULL) {
    fd = sim_fd;
  } else {
    fd = fopen(filename, "w+");
  }
  assert(fd);
  sim_dump_stats(fd);
  fclose(fd);
}

//Enable simulation
void sim_start()
{
  sim_cpu_clock_offset = cpu_get_clock();
  sim_cpu_ticks_offset = cpu_get_ticks();
  sim_enable();
}

//Stop simulation
void sim_stop()
{
  //call device stop functions..
  int i=0;
  for(i=0; i<num_devices; i++) {
    if(sim_devices[i].stop_cb != NULL)
      sim_devices[i].stop_cb();
  }

  sim_disable();
  sim_cpu_clock_offset = 0;
  sim_dump_stats(sim_fd);
  fflush(sim_fd);
}

//Find a device using a dev pointer
SimDevice* sim_find_dev_with_addr(uint64_t addr, int size) {
  int j, bsize;
  switch(size) {
    case 1 :
      bsize = 0; 
      break;
    case 2 :
      bsize = 1;
      break;
    case 4 :
      bsize = 2;
      break;
    default:
      assert(0);
  }
  return sim_dev_io_addr[bsize][addr];
}

SimDevice* sim_get_dev_by_id(int id) {
  return &sim_devices[id];
}

//register a device
//int sim_register_device(enum DeviceType type, const char* name, void* dev, uint64_t addr, int len, int size, uint8_t mmap) {
//
//  int i;
//
//  //find the device, if we already have it then just update the addr hash map
//  for(i=0; i<num_devices; i++) {
//    if(sim_devices[i].dev == dev) {
//      int j;
//      for(j=addr; j < addr + len; j += size) {
//	sim_dev_io_addr[j] = &sim_devices[i];
//	simdebug("Added device %s to Addr: %04x\n", sim_devices[i].name,
//	    j);
//      }
//      return i;
//    }
//  }
//
//  i = num_devices;
//  sim_devices = realloc(sim_devices, sizeof(SimDevice) * (++num_devices));
//  assert(sim_devices != NULL);
//
//  sim_devices[i].type = type;
//  pstrcpy(sim_devices[i].name, sizeof(sim_devices[i].name), name);
//  sim_devices[i].dev = dev;
//  sim_devices[i].mmap = mmap;
//  int j;
//  for(j = addr; j < addr + len; j += size) {
//    sim_dev_io_addr[j] = &sim_devices[i];
//  }
//  sim_devices[i].bindings = NULL;
//  if(sim_devices[i].type == PCI_BRIDGE) 
//    bridge = i;
//  else if(sim_devices[i].type == PCI_BUS) 
//    pci_bus = i;
//  else if(sim_devices[i].type == CPU)
//    cpu = i;
//
//  simdebug("Register Device:- Type: %s, Name: %s, Addr: %04x, MMAP: %02x\n", 
//      DeviceTypeNames[type], sim_devices[i].name,
//      addr, sim_devices[i].mmap);
//
//  return i;
//}

SimDevice* sim_register_device(enum DeviceType type, const char* name, void* dl, uint8_t mmap) {

  int i;

  //find the device, if we already have it then just update the addr hash map
//  for(i=0; i<num_devices; i++) {
//    if(sim_devices[i].dev == dev) {
//      int j;
//      for(j=addr; j < addr + len; j += size) {
//	sim_dev_io_addr[j] = &sim_devices[i];
//	simdebug("Added device %s to Addr: %04x\n", sim_devices[i].name,
//	    j);
//      }
//      return i;
//    }
//  }
  if(num_devices == MAX_DEVICES ) {
    fprintf(stderr,"PANIC!! No more space avilable for new devices..\n");
    exit(-1);
  }

  i = num_devices;
  num_devices ++;
//  sim_devices = realloc(sim_devices, sizeof(SimDevice) * (++num_devices));
//  assert(sim_devices != NULL);

  sim_devices[i].type = type;
  pstrcpy(sim_devices[i].name, sizeof(sim_devices[i].name), name);
  sim_devices[i].id = i;
//  sim_devices[i].dev = dev;
  sim_devices[i].mmap = mmap;
  sim_devices[i].bindings = NULL;
  switch(sim_devices[i].type) {
    case PCI_BRIDGE:
      pci_bridge = i;
      break;
    case PCI_BUS:
      pci_bus = i;
      break;
    case CPU:
      cpu = i;
      break;
    case CACHE:
      cache = i;
      break;
    case ISA_BRIDGE:
      isa_bridge = i;
      break;
    case PCI_IDE:
      pci_ide = i;
      break;
    case PCI_NETWORK_CARD:
      pci_network_card = i;
      break;
    default:
      break;
  }

  sim_devices[i].dl_handler = dl;

  sim_devices[i].read_config_cb = NULL;
  sim_devices[i].write_config_cb = NULL;
  sim_devices[i].in = NULL;
  sim_devices[i].stop_cb = NULL;
  sim_devices[i].init_cb = NULL;

  simdebug("Register Device:- Type: %s, Name: %s\n", 
      DeviceTypeNames[type], sim_devices[i].name);

  return &sim_devices[i];
}

//Register device address space
void sim_register_addr(SimDevice* dev, uint64_t addr, int len, int size)
{
  int j, bsize;
  switch(size) {
    case 1 :
      bsize = 0; 
      break;
    case 2 :
      bsize = 1;
      break;
    case 4 :
      bsize = 2;
      break;
    default:
      assert(0);
  }
  for(j = addr; j < addr + len; j += size) {
    sim_dev_io_addr[bsize][j] = dev;
  }
}

//reigster pci-device of qemu to simulator..
void sim_register_qemu_device(SimDevice* dev, void* p_dev)
{
  dev->dev = p_dev;
}

//register callback functions
void sim_read_config_cb(SimDevice *dev, SimDeviceReadConfigFunc *_cb) {
//  int i=0;
//  for(i=0; i<num_devices; i++) {
//    if(sim_devices[i].dev == dev) {
//      sim_devices[i].read_config_cb = _cb;
//
  dev->read_config_cb = _cb;
      simdebug("Register read config callback function of device %s\n",
	  dev->name);
//    }
//  }
}

void sim_write_config_cb(SimDevice *dev, SimDeviceWriteConfigFunc *_cb) {
//  int i=0;
//  for(i=0; i<num_devices; i++) {
//    if(sim_devices[i].dev == dev) {
//    sim_devices[i].write_config_cb = _cb;
  dev->write_config_cb = _cb;

    simdebug("Register write config callback function of device %s\n",
	dev->name);
//    }
//  }
}

void sim_register_in_cb(SimDevice *dev, SimDeviceIOFunc* _cb)
{
//  int i=0;
//  for(i=0; i<num_devices; i++) {
//    if(sim_devices[i].dev == dev) {
//      sim_devices[i].in = _cb;
//      return;
//    }
//  }
  dev->in = _cb;
}

void sim_register_simulation_stop_cb(SimDevice* dev, SimDeviceStopFunc* _cb)
{
  dev->stop_cb = _cb;
}

void sim_register_init_cb(SimDevice* dev, SimDeviceInitFunc* _cb)
{
  dev->init_cb = _cb;
}

void sim_set_pic(qemu_irq* _pic)
{
  sim_pic = _pic;
}

qemu_irq* sim_get_pic()
{
  return sim_pic;
}


//void sim_register_out_cb(void *dev, SimDeviceIOFunc* _cb)
//{
//  int i=0;
//  for(i=0; i<num_devices; i++) {
//    if(sim_devices[i].dev == dev) {
//      sim_devices[i].out = _cb;
//      return;
//    }
//  }
//}

int simulate_io_write(uint64_t addr, uint64_t data, int size, void* opaque) {
  //find the device of given addr
  SimDevice* sim_dev = sim_find_dev_with_addr(addr, size);
  if(sim_dev == NULL) {
    //simdebug("Cant find dev at addr %04x for write\n", addr);
    return -1;
  }

//  sim_dev->write_config_cb(opaque, addr, data, size);
//  simdebug("Data %04x is written to Device %s at Addr %04x\n",
//      data, sim_dev->name, addr);
  //Now we always forward the request to the bridge
//  sim_devices[bridge].write_config_cb(opaque, addr, data, size);
  Message* msg = sim_new_write_msg(opaque, addr, data, size);
  sim_devices[pci_bridge].write_config_cb(msg);
  return 0;
}

int simulate_io_read(uint64_t addr, uint64_t *data, int size, void* opaque) {
  //find device
  SimDevice* sim_dev = sim_find_dev_with_addr(addr, size);
  if(sim_dev == NULL) {
    //simdebug("Can't find dev at addr %04x for read\n", addr);
    return -1;
  }

  Message* msg = sim_new_read_msg(opaque, addr, size);
  *data = sim_dev->read_config_cb(msg);
  simdebug("Data %04x is read at Addr %04x from Device %s\n", 
      *data, addr, sim_dev->name);
  return 0;
}

//This function forward message from one device to other device
//The new device has to be registered into the simulation
//void sim_forward_msg(Message* msg)
//{
//  SimDevice* dev = sim_find_dev_with_addr(msg->addr);
//  if(msg->type == READ_MSG)
//    //dev->read_config_cb(msg->opaque,msg->addr,msg->size);
//}

//Simulator's clock functions

int64_t sim_get_cpu_clock()
{
  //FIXME
  //sim_get_clock() provides the ticks not the clock..
  return sim_cpu_clock_offset + sim_get_clock();
}

int64_t sim_get_cpu_ticks()
{
  return sim_cpu_ticks_offset + sim_get_clock();
}

//void sim_cache_access(target_long addr)
//{
//  simdebug("Cache access addr:%e\n",(unsigned long)addr);
//}

void sim_cache_access(unsigned long addr, int data_size, uint8_t type)
{
  
//  assert(&sim_devices[cache] != NULL);
  if(cache == -1)
    return;
//  if(&sim_devices[cache] == NULL)// || !SIMULATE_CACHE) 
//    return;

  Message *msg;

  if(type) {
    msg = sim_new_write_msg(NULL, addr, 0, (int)data_size);
  } else {
    msg = sim_new_read_msg(NULL, addr, (int)data_size);
  }

  msg->from = &sim_devices[cpu];
  sim_insert_cb(sim_devices[cache].in, msg, 0);
  sim_do_callback();
//  sim_devices[cache].in(msg);
//    simdebug("Cache access addr:%04x\n",a);
//  printf("Cache access addr:%04x\n",a);
}

//Simulation functions

void sim_vm_stop()
{
  sim_term_print("\nSpecial Sim Instruction hit, stopping the VM\n");
  vm_stop(0);
}


void sim_init_bridge()
{
  assert(&sim_devices[pci_bridge] != NULL);
  sim_devices[pci_bridge].init_cb();
}

void sim_set_pci_bridge_dev(PCIDevice* dev)
{
  sim_pci_bridge_dev = dev;
}

PCIDevice* sim_get_pci_bridge_dev()
{
  return sim_pci_bridge_dev;
}

void sim_set_pci_bus(PCIBus* bus)
{
  sim_pci_bus = bus;
}

PCIBus* sim_get_pci_bus()
{
  return sim_pci_bus;
}

void sim_set_isa_bridge_dev(PCIDevice* dev)
{
  sim_isa_bridge_dev = dev;
}

PCIDevice* sim_get_isa_bridge_dev()
{
  return sim_isa_bridge_dev;
}

void sim_register_smm_cb(SimDeviceSMMFunc* _smm_cb)
{
  smm_func = _smm_cb;
}

void sim_set_smm(int val)
{
  smm_func(val);
}

//Wrapper function for cpu_get_physical_page_desc
uint32_t sim_get_physical_page_desc(target_phys_addr_t addr)
{
  return cpu_get_physical_page_desc(addr);
}

//Memory mappings
void sim_register_init_mem_map_cb(SimDeviceMemInitFunc* _mmap_cb)
{
  sim_mmap_init_func = _mmap_cb;
}

void sim_init_mmap()
{
  sim_mmap_init_func();
}

void sim_set_ide_devfn(int devfn)
{
  sim_ide_devfn = devfn;
}

int sim_get_ide_devfn()
{
  return sim_ide_devfn;
}

void sim_set_hd_table(BlockDriverState** hd)
{
  sim_hd_table = hd;
}

BlockDriverState** sim_get_hd_table()
{
  return sim_hd_table;
}

void sim_init_ide()
{
  sim_devices[pci_ide].init_cb();
}

void sim_init_network_card()
{
  sim_devices[pci_network_card].init_cb();
}

void sim_set_nicinfo(NICInfo* nd)
{
  sim_nicinfo = nd;
}

NICInfo* sim_get_nicinfo()
{
  return sim_nicinfo;
}

//Block driver Wrapper functions
BlockDriverAIOCB *sim_bdrv_aio_write(BlockDriverState *bs, int64_t sector_num,
                                 const uint8_t *buf, int nb_sectors,
                                 BlockDriverCompletionFunc *cb, void *opaque)
{
  return bdrv_aio_write(bs, sector_num, buf, nb_sectors, cb, opaque);
}

BlockDriverAIOCB *sim_bdrv_aio_read(BlockDriverState *bs, int64_t sector_num,
                                 const uint8_t *buf, int nb_sectors,
                                 BlockDriverCompletionFunc *cb, void *opaque)
{
  return bdrv_aio_read(bs, sector_num, buf, nb_sectors, cb, opaque);
}

void sim_bdrv_aio_cancel(BlockDriverAIOCB *acb) 
{
  bdrv_aio_cancel(acb);
}

void sim_bdrv_get_geometry(BlockDriverState *bs, uint64_t *nb_sectors_ptr)
{
  bdrv_get_geometry(bs, nb_sectors_ptr);
}

void sim_bdrv_set_change_cb(BlockDriverState *bs,
                        void (*change_cb)(void *opaque), void *opaque)
{
  bdrv_set_change_cb(bs, change_cb, opaque);
}

void sim_bdrv_eject(BlockDriverState *bs, int eject_flag)
{
  bdrv_eject(bs, eject_flag);
}

int sim_cdrom_read_toc(int nb_sectors, uint8_t *buf, int msf, int start_track)
{
  return cdrom_read_toc(nb_sectors, buf, msf, start_track);
}

int sim_cdrom_read_toc_raw(int nb_sectors, uint8_t *buf, int msf, int session_num)
{
  return cdrom_read_toc_raw(nb_sectors, buf, msf, session_num);
}

int sim_bdrv_get_type_hint(BlockDriverState *bs)
{
  return bdrv_get_type_hint(bs);
}

void sim_bdrv_set_locked(BlockDriverState *bs, int locked)
{
  bdrv_set_locked(bs, locked);
}

void sim_bdrv_flush(BlockDriverState *bs)
{
  bdrv_flush(bs);
}

IOPortReadFunc* get_ioport_read_func(uint32_t addr, uint8_t size)
{
  uint8_t bsize;
  switch(size) {
    case 1 :
      bsize = 0; 
      break;
    case 2 :
      bsize = 1;
      break;
    case 4 :
      bsize = 2;
      break;
    default:
      assert(0);
  }
  return ioport_read_table[bsize][addr];
}

IOPortWriteFunc* get_ioport_write_func(uint32_t addr, uint8_t size)
{
  uint8_t bsize;
  switch(size) {
    case 1 :
      bsize = 0; 
      break;
    case 2 :
      bsize = 1;
      break;
    case 4 :
      bsize = 2;
      break;
    default:
      assert(0);
  }
  return ioport_write_table[bsize][addr];
}
